﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Logic.ClarizenSvc;
using System.Diagnostics;
using System.ComponentModel;
using System.Xml.Serialization;
using System.Web;

namespace Logic
{

    public static class ClarizenUtils
    {
        public static void CheckResponseStatus(Result[] results)
        {
            foreach (Result result in results)
                CheckResponseStatus(result);
        }

        public static void CheckResponseStatus(Result result)
        {
            if (!result.Success)
                throw new Exception(result.Error.Message);
        }

        public static string FieldValueToString(FieldValue fieldValue)
        {
            if (fieldValue == null || fieldValue.Value == null)
                return String.Empty;

            if (fieldValue.Value is GenericEntity)
                return String.Join(",", ((GenericEntity)fieldValue.Value).Values.Select(fv => "{" + fv.FieldName + ":" + FieldValueToString(fv) + "}").ToArray());
            else if (fieldValue.Value is DateTime)
            {
                DateTime dt = (DateTime)fieldValue.Value;
                dt = dt.ToLocalTime();
                return dt.ToString("yyyy-MM-dd HH:mm");
            }
            else
                return fieldValue.Value.ToString();
        }

        public static String FieldValueToObject(FieldValue fieldValue)
        {
            if (fieldValue == null || fieldValue.Value == null)
                return String.Empty;

            if (fieldValue.Value is GenericEntity)
                return String.Join(",", ((GenericEntity)fieldValue.Value).Values.Select(fv => "{" + fv.FieldName + ":" + FieldValueToString(fv) + "}").ToArray());
            else if (fieldValue.Value is DateTime)
            {
                DateTime dt = (DateTime)fieldValue.Value;
                dt = dt.ToLocalTime();
                return dt.ToString("yyyy-MM-dd HH:mm");
            }
            else if (fieldValue.Value is double || fieldValue.Value is int || fieldValue.Value is float || fieldValue.Value is decimal || fieldValue.Value is long)
                return fieldValue.Value.ToString();
            else
                return fieldValue.Value.ToString();
        }

        public static Dictionary<string, FieldValue> ConvertToDictionary(FieldValue[] fieldValues)
        {
            Dictionary<string, FieldValue> result = new Dictionary<string, FieldValue>(fieldValues.Length);
            foreach (FieldValue fv in fieldValues)
            {
                result[fv.FieldName] = fv;
            }
            return result;
        }

        //public static string GetEntitySubField(FieldValue fieldValue, int subFieldIndex)
        //{
        //    GenericEntity entity = fieldValue.Value as GenericEntity;
        //    if (entity != null)
        //    {
        //        if (subFieldIndex < entity.Values.Length)
        //        {
        //            return FieldValueToString(entity.Values[subFieldIndex]);
        //        }
        //    }
        //    return null;
        //}

        public static string CounterToString(FieldValue fieldValue)
        {
            int? value = (int?)fieldValue.Value;
            if (value > 0)
                return value.ToString();
            return String.Empty;
        }

        static byte[] ent = System.Text.Encoding.Unicode.GetBytes("oiquwertmnxdvoiu");

        public static string Decrypt(string s)
        {
            try
            {
                byte[] decryptedData = System.Security.Cryptography.ProtectedData.Unprotect(
                    Convert.FromBase64String(s),
                    ent,
                    System.Security.Cryptography.DataProtectionScope.CurrentUser);
                return System.Text.Encoding.Unicode.GetString(decryptedData);
            }
            catch
            {
                return null;
            }
        }

        public static string Encrypt(string s)
        {
            byte[] encryptedData = System.Security.Cryptography.ProtectedData.Protect(
                System.Text.Encoding.Unicode.GetBytes(s),
                ent,
                System.Security.Cryptography.DataProtectionScope.CurrentUser);
            return Convert.ToBase64String(encryptedData);
        }

        class TimingHelper : IDisposable
        {
            Stopwatch sw = Stopwatch.StartNew();

            public void Dispose()
            {
                sw.Stop();
                if (ClarizenUtils.TimingUpdate != null)
                    ClarizenUtils.TimingUpdate(sw.ElapsedMilliseconds);
            }
        }

        public static IDisposable StartTiming()
        {
            return new TimingHelper();
        }

        public delegate void TimingEvent(long milliseconds);

        public static event TimingEvent TimingUpdate;

        public delegate void BackGroundWork<T2>(T2 param);
        public static void DoInBackGround<T>(BackGroundWork<T> method, T param)
        {
            BackgroundWorker bw = new BackgroundWorker { WorkerSupportsCancellation = true };
            bw.DoWork += (sender, e) =>
            {
                method(param);
            };
            bw.RunWorkerAsync();
        }

        public static String GetGoTo()
        {
            try
            {
                var goTo = HttpContext.Current.Request["goto"];
                if (!string.IsNullOrEmpty(goTo) && new Uri(goTo).Authority == HttpContext.Current.Request.Url.Authority)
                    return goTo;
            }
            catch { }
            return Logic.Constants.CurrentUrl + "/Tasks/Index";
        }
    }
}



namespace Logic.ClarizenSvc
{
    public partial class Query
    {
        [XmlIgnore]
        public int? Count { get; set; }
    }

    public partial class FieldDescription
    {
        public virtual string GetDataType()
        {
            return this.Type.ToString();
        }

        public override string ToString()
        {
            return string.Format("{0} ({1})", this.Label, this.GetDataType());
        }
        [XmlIgnore]
        public String TypeName
        {
            get { return Type.ToString(); }
        }
    }

    public partial class EntityFieldDescription
    {
        public override string GetDataType()
        {
            return string.Join(",", this.ReferencedEntities);
        }
    }

    public partial class EntityDescription
    {
        Dictionary<string, FieldDescription> _fields;

        public override string ToString()
        {
            return this.TypeName;
        }

        public FieldDescription this[string fieldName]
        {
            get
            {
                if (_fields == null)
                {
                    _fields = new Dictionary<string, FieldDescription>();
                    foreach (FieldDescription fd in this.Fields)
                        _fields[fd.Name] = fd;
                }
                FieldDescription result;
                if (_fields.TryGetValue(fieldName, out result))
                    return result;
                return null;
            }
        }
    }

    public partial class LinkEntityDescription
    {
        public override string ToString()
        {
            return this.TypeName + " (Link)";
        }
    }

    public partial class PickupEntityDescription
    {
        public override string ToString()
        {
            return this.TypeName + " (Pickup)";
        }
    }

    public partial class PickupDescription
    {
        public override string ToString()
        {
            return this.Value + " (" + this.Description + ")";
        }
    }

    public partial class Result
    {
        public override string ToString()
        {
            string name = this.GetType().Name;
            if (this.Success)
                return name + ": OK";
            if (this.Error != null)
                return name + ": Failed, " + Error.Message;
            return name + ": Failed";
        }
    }

    public partial class BaseMessage
    {
        public override string ToString()
        {
            return this.GetType().Name;
        }
    }

    public partial class Compare
    {
        public override string ToString()
        {
            return string.Format("{0} {1} {2}", LeftExpression, Operator, RightExpression);
        }
    }

    public partial class Duration
    {
        public override string ToString()
        {
            return string.Format("{0} {1}", Value, Unit);
        }

        public static Duration operator +(Duration d1, Duration d2)
        {
            if (d1 == null)
                return d2;
            if (d2 == null)
                return d1;

            if (d1.Unit > d2.Unit)
                d1.ConvertUnit(d2.Unit);
            else d2.ConvertUnit(d1.Unit);
            return new Duration {Unit = d1.Unit, Value = d1.Value + d2.Value};
        }

        private void ConvertUnit(DurationUnit newUnit)
        {
            var isDown = this.Unit > newUnit;

            while (this.Unit != newUnit)
            {
                switch (this.Unit)
                {
                    case DurationUnit.Months:
                        this.Value *= isDown? 4: 1/4;
                        break;
                    case DurationUnit.Weeks:
                        this.Value *= isDown? 5: 1/5;
                        break;
                    case DurationUnit.Days:
                        this.Value *= isDown? 9: 1/9;
                        break;
                    case DurationUnit.Hours:
                        this.Value *= isDown? 60: 1/60;
                        break;
                }

                this.Unit += isDown ? -1 : 1;
            }
        }
    }

    public partial class Money
    {
        public override string ToString()
        {
            return string.Format("{0} {1}", Value, Currency);
        }
    }

    public partial class FieldExpression
    {
        public override string ToString()
        {
            return this.GetFieldDisplayName();
        }
        [XmlIgnore]
        private String fieldDisplayName;
        public void SetFieldDisplayName(String str)
        {
            fieldDisplayName = str;
        }
        public String GetFieldDisplayName()
        {
            return !String.IsNullOrEmpty(fieldDisplayName)? fieldDisplayName: FieldName;
        }
    }

    public partial class ConstantExpression
    {
        public override string ToString()
        {
            return (Value == null ? "null" : Value.ToString());
        }
    }

    public partial class GenericEntity
    {
        public String Name
        {
            get
            {
                return GetFieldValue("Name");
            }
        }
        
        public Object[] ToValueArray(bool withID)
        {
            List<Object> result = new List<Object>();
            if (withID)
            {
                result.Add(this.Id.Value);
                result.Add(this.Id.TypeName);
            }
            foreach (FieldValue fv in this.Values)
            {
                if (fv.Value is GenericEntity)
                    result.AddRange(((GenericEntity)fv.Value).ToValueArray(false));
                else
                    result.Add(ClarizenUtils.FieldValueToObject(fv));
            }

            return result.ToArray();
        }

        public KeyValuePair<String, Object> GetKeyValuePair()
        {
            var key = ((GenericEntity)this).Values[0].Value.ToString();
            var value = ((GenericEntity)this).Id;
            value.DisplayName = key;
            return new KeyValuePair<String, Object>(key, value);
        }

        internal String GetFieldValue(string field)
        {
            return GetFieldValue(new string[] { field });
        }

        internal String GetFieldValue(string[] field)
        {
            // this should ot happen
            if (field == null || field.Length == 0 || this.Values == null)
                return null;
            else
            {
                FieldValue fv = this.Values.FirstOrDefault(v => v.FieldName == field[0]);
                if (field.Length == 1) // simple field
                    return ClarizenUtils.FieldValueToObject(fv);
                else // sub field
                {
                    if (fv == null || !(fv.Value is GenericEntity)) // there aren't any sub values
                        return null;
                    else // sub values exist we'll return the value from there
                    {
                        var subField = new String[field.Length - 1];
                        Array.Copy(field, 1, subField, 0, field.Length - 1);
                        return ((GenericEntity)fv.Value).GetFieldValue(subField);
                    }
                }
            }
        }
    }

    public partial class EntityId
    {
        public override string ToString()
        {
            return string.Format("{0} ({1})", this.DisplayName, this.TypeName);
        }
        [XmlIgnore]
        public String DisplayName { get; set; }
    }

    public interface ICondition
    {
        List<Condition> GetClarizenCondition();
    }

    public class BetweenCondition : Condition, ICondition
    {
        public Object UpperBound { get; set; }
        public Object LowerBound { get; set; }
        public Expression LeftExpression {get;set;}

        public List<Condition> GetClarizenCondition()
        {
            var Cond1 = new Compare { LeftExpression = this.LeftExpression, Operator = ClarizenSvc.Operator.GreaterThanOrEqual, RightExpression = new ConstantExpression { Value=this.LowerBound} };
            var Cond2 = new Compare { LeftExpression = this.LeftExpression, Operator = ClarizenSvc.Operator.LessThanOrEqual, RightExpression = new ConstantExpression { Value = this.UpperBound } };
            return new List<Condition> { Cond1, Cond2 };
        }

        public override string ToString()
        {
            return String.Format("{0} between {1} and {2}", this.LeftExpression.ToString(), this.LowerBound, this.UpperBound);
        } 
             
    }

    public class DateRangeCondition : Condition, ICondition
    {
        public static void GetBounds(DateRanges Range, out DateTime lowerBound, out DateTime upperBound)
        {
            var startOfMonth = DateTime.Today.AddDays(-DateTime.Today.Day + 1);
            var startOfYear = DateTime.Today.AddDays(-DateTime.Today.DayOfYear + 1);
            var startOfQuarter = startOfMonth.AddMonths(-((startOfMonth.Month - 1) % 3));
            var startOfWeek = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek);
            var today = DateTime.Today;

            switch (Range)
            {
                case DateRanges.Last_Month:
                    lowerBound = startOfMonth.AddMonths(-1);
                    upperBound = startOfMonth;
                    break;
                case DateRanges.This_Month:
                    lowerBound = startOfMonth;
                    upperBound = startOfMonth.AddMonths(1);
                    break;
                case DateRanges.Next_Month:
                    lowerBound = startOfMonth.AddMonths(1);
                    upperBound = startOfMonth.AddMonths(2);
                    break;
                case DateRanges.Current_And_Previous_Month:
                    lowerBound = startOfMonth.AddMonths(-1);
                    upperBound = startOfMonth.AddMonths(1);
                    break;
                case DateRanges.Current_And_Next_Month:
                    lowerBound = startOfMonth;
                    upperBound = startOfMonth.AddMonths(2);
                    break;

                case DateRanges.Current_CY:
                    lowerBound = startOfYear;
                    upperBound = startOfYear.AddYears(1);
                    break;
                case DateRanges.Previous_CY:
                    lowerBound = startOfYear.AddYears(-1);
                    upperBound = startOfYear;
                    break;
                case DateRanges.Previous_2_CY:
                    lowerBound = startOfYear.AddYears(-2);
                    upperBound = startOfYear;
                    break;
                case DateRanges.Two_CY_Ago:
                    lowerBound = startOfYear.AddYears(-2);
                    upperBound = startOfYear.AddYears(-1);
                    break;
                case DateRanges.Next_CY:
                    lowerBound = startOfYear.AddYears(1);
                    upperBound = startOfYear.AddYears(2);
                    break;
                case DateRanges.Current_And_Previous_CY:
                    lowerBound = startOfYear.AddYears(-1);
                    upperBound = startOfYear.AddYears(1);
                    break;
                case DateRanges.Current_And_Previous_2_CY:
                    lowerBound = startOfYear.AddYears(-2);
                    upperBound = startOfYear.AddYears(1);
                    break;
                case DateRanges.Current_And_Next_CY:
                    lowerBound = startOfYear;
                    upperBound = startOfYear.AddYears(2);
                    break;



                case DateRanges.Current_CQ:
                    lowerBound = startOfQuarter;
                    upperBound = startOfQuarter.AddMonths(3);
                    break;
                case DateRanges.Previous_CQ:
                    lowerBound = startOfQuarter.AddMonths(-3);
                    upperBound = startOfQuarter;
                    break;
                case DateRanges.Next_CQ:
                    lowerBound = startOfQuarter.AddMonths(3);
                    upperBound = startOfQuarter.AddMonths(6);
                    break;
                case DateRanges.Current_And_Previous_CQ:
                    lowerBound = startOfQuarter.AddMonths(-3);
                    upperBound = startOfQuarter.AddMonths(3);
                    break;
                case DateRanges.Current_And_Next_3_CQ:
                    lowerBound = startOfQuarter;
                    upperBound = startOfQuarter.AddMonths(12);
                    break;
                case DateRanges.Current_And_Next_CQ:
                    lowerBound = startOfQuarter;
                    upperBound = startOfQuarter.AddMonths(6);
                    break;

                case DateRanges.This_Week:
                    lowerBound = startOfWeek;
                    upperBound = startOfWeek.AddDays(7);
                    break;
                case DateRanges.Last_Week:
                    lowerBound = startOfWeek.AddDays(-7);
                    upperBound = startOfWeek;
                    break;
                case DateRanges.Next_Week:
                    lowerBound = startOfWeek.AddDays(7);
                    upperBound = startOfWeek.AddDays(14);
                    break;


                case DateRanges.Today:
                    lowerBound = today;
                    upperBound = today.AddDays(1);
                    break;
                case DateRanges.Yesterday:
                    lowerBound = today.AddDays(-1);
                    upperBound = today;
                    break;
                case DateRanges.Tomorrow:
                    lowerBound = today.AddDays(1);
                    upperBound = today.AddDays(2);
                    break;
                case DateRanges.Last_7_Days:
                    lowerBound = today.AddDays(-7);
                    upperBound = today;
                    break;
                case DateRanges.Last_30_days:
                    lowerBound = today.AddDays(-30);
                    upperBound = today;
                    break;
                case DateRanges.Last_60_Days:
                    lowerBound = today.AddDays(-60);
                    upperBound = today;
                    break;
                case DateRanges.Last_90_Days:
                    lowerBound = today.AddDays(-90);
                    upperBound = today;
                    break;
                case DateRanges.Last_120_Days:
                    lowerBound = today.AddDays(-120);
                    upperBound = today;
                    break;
                case DateRanges.Next_7_Days:
                    lowerBound = today;
                    upperBound = today.AddDays(7);
                    break;
                case DateRanges.Next_30_days:
                    lowerBound = today;
                    upperBound = today.AddDays(30);
                    break;
                case DateRanges.Next_60_Days:
                    lowerBound = today;
                    upperBound = today.AddDays(60);
                    break;
                case DateRanges.Next_90_Days:
                    lowerBound = today;
                    upperBound = today.AddDays(90);
                    break;
                case DateRanges.Next_120_Days:
                    lowerBound = today;
                    upperBound = today.AddDays(120);
                    break;

                default:
                    lowerBound = DateTime.Now;
                    upperBound = DateTime.Now;
                    break;
            }
        }

        public static void GetBounds(String range, out DateTime lowerBound, out DateTime upperBound)
        {
            GetBounds((DateRanges)Enum.Parse(typeof(DateRanges), range), out lowerBound, out upperBound);
        }

        public FieldExpression LeftExpression { get; set; }
        public DateRanges DateRange { get; set; }

        public List<Condition> GetClarizenCondition()
        {
            Compare lowerBound, upperBound;
            DateTime start, end;
            GetBounds(this.DateRange, out start, out end);
            lowerBound = new Compare { LeftExpression = this.LeftExpression, Operator = Operator.GreaterThanOrEqual, RightExpression = new ConstantExpression { Value = start } };
            upperBound = new Compare
            {
                LeftExpression = this.LeftExpression,
                Operator = Operator.LessThanOrEqual,
                RightExpression = new ConstantExpression { Value = end }
            };

            return new List<Condition> { lowerBound, upperBound };
        }

        public DateRangeCondition(String range)
        {
            range = range.Replace(" ", "_");
            this.DateRange = (DateRanges)Enum.Parse(typeof(DateRanges), range);
        }
        public DateRangeCondition(DateRanges range)
        {
            this.DateRange = range;
        }
        public DateRangeCondition()
        {
        }

        public override string ToString()
        {
            return String.Format("{0} in {1}", LeftExpression.ToString() , this.DateRange.ToString().Replace("_"," "));
        }
    }

    public enum DateRanges
    {

        Last_Month,
        This_Month,
        Next_Month,
        Current_And_Previous_Month,
        Current_And_Next_Month,

        Current_CY,
        Previous_CY,
        Previous_2_CY,
        Two_CY_Ago,
        Next_CY,
        Current_And_Previous_CY,
        Current_And_Previous_2_CY,
        Current_And_Next_CY,

        Current_CQ,
        Previous_CQ,
        Next_CQ,
        Current_And_Previous_CQ,
        Current_And_Next_3_CQ,
        Current_And_Next_CQ,

        Last_Week,
        This_Week,
        Next_Week,

        Yesterday,
        Today,
        Tomorrow,
        Last_7_Days,
        Last_30_days,
        Last_60_Days,
        Last_90_Days,
        Last_120_Days,
        Next_7_Days,
        Next_30_days,
        Next_60_Days,
        Next_90_Days,
        Next_120_Days

    }

    public partial class EntityQueryWrapper
    {
        public Dictionary<String, String> FieldNames { get; set; }
        public EntityQuery Query { get; set; }
        private String _Version = null;
        public String Version 
        {
            get 
            {
                if(String.IsNullOrEmpty(_Version)) 
                    return "1.1"; 
                else return _Version;
            }
            set
            {
                _Version = value;
            }
        }
    }

    

    
}
